/*******************************************************************************
 * Copyright (c) 2012, 2013 XML-Print Project.
 * All rights reserved. This program and the accompanying materials
 * are made available under the terms of the Eclipse Public License v1.0
 * which accompanies this distribution, and is available at
 * http://www.eclipse.org/legal/epl-v10.html
 * 
 * Contributors:
 *     XML-Print Project - initial API and implementation
 ******************************************************************************/
package info.xmlprint.font;

import info.xmlprint.core.mapping.Mapping;
import info.xmlprint.core.mapping.MappingGroup;
import info.xmlprint.run.Configuration;

import java.io.File;
import java.util.ArrayList;
import java.util.Collections;
import java.util.Comparator;
import java.util.HashMap;
import java.util.HashSet;
import java.util.List;
import java.util.Map;
import java.util.Set;

import org.apache.log4j.Logger;
import org.dom4j.Document;
import org.dom4j.DocumentHelper;
import org.dom4j.Element;

import com.itextpdf.text.pdf.BaseFont;

/*
 * Font-Dateien in angegebenden Verzeichnis lesen und Informationen sammeln
 * 
 */
public class FontManager {

	private static FontManager fontManager;

	private static List<FontInfo> fontInfoList;

	private String[] fontNames;

	private Set<String> nameSet;

	private Set<String> lowercaseNameSet;

	private Map<String, List<FontInfo>> familyMap;

	private static Logger logger = Logger.getLogger(FontManager.class
			.toString());

	public static FontManager getInstance() {
		if (fontManager == null) {
			synchronized (FontManager.class) {
				if (fontManager == null) {
					fontManager = new FontManager();
				}
			}
		}
		return fontManager;
	}

	private FontManager() {
		init();
	}

	private void addFamily(String lowercaseName, FontInfo fi) {
		List<FontInfo> list = familyMap.get(lowercaseName);
		if (list == null) {
			list = new ArrayList<FontInfo>();
			familyMap.put(lowercaseName, list);
		}
		list.add(fi);
	}

	public List<FontInfo> getFamilyList(String name) {
		if (name == null)
			return null;
		List<FontInfo> list = familyMap.get(name.toLowerCase());
		return list;
	}

	private void init() {
		final Configuration conf = Configuration.getInstance();
		fontInfoList = new ArrayList<FontInfo>();
		nameSet = new HashSet<String>();
		familyMap = new HashMap<String, List<FontInfo>>();
		lowercaseNameSet = new HashSet<String>();
		logger.info("Start registering fonts\n");
		registerDirectories(new String[] { conf.getSystemFontsDir(),
				conf.getXmlprintFontsDir(), conf.getUserFontsDir() });
		sortList();
		logger.info("Local Fonts registered\n");
	}

	public FontInfo getFontInfo(String familyName, String nameStyle,
			String fontWeight) {
		if (nameStyle.equalsIgnoreCase("normal")) {
			nameStyle = "regular";
		}

		familyName = familyName.toLowerCase().trim();
		nameStyle = nameStyle.toLowerCase().trim();
		fontWeight = fontWeight.toLowerCase().trim();
		if (fontWeight.equalsIgnoreCase("700")) {
			fontWeight = "bold";
		} else if (fontWeight.equalsIgnoreCase("400")) {
			fontWeight = "";
		}

		List<FontInfo> list = getFamilyList(familyName);

		if (list == null || list.size() < 1)
			return null;

		String fontVariables;
		for (FontInfo fi : list) {
			// font name stimmt?
			if (fi.getFontFamilyName().equalsIgnoreCase(familyName)) {
				fontVariables = fi.getFontVariable().toLowerCase();

				// regular
				if (nameStyle.equalsIgnoreCase("regular")
						&& fontVariables.contains("regular")) {
					return fi;
				} else {
					// bold italic
					if (fontVariables.contains("bold")
							&& fontWeight.equalsIgnoreCase("bold")
							&& fontVariables.contains("italic")
							&& nameStyle.equalsIgnoreCase("italic")) {
						return fi;
					}

					// bold
					if (fontVariables.contains("bold")
							&& fontWeight.equalsIgnoreCase("bold")
							&& !fontVariables.contains("italic")
							&& !nameStyle.equalsIgnoreCase("italic")) {
						return fi;
					}

					// italic
					if (!fontVariables.contains("bold")
							&& !fontWeight.equalsIgnoreCase("bold")
							&& fontVariables.contains("italic")
							&& nameStyle.equalsIgnoreCase("italic")) {
						return fi;
					}
				}
			}
		}
		return null;
	}

	private void registerDirectories(String[] fontDirs) {
		for (String dir : fontDirs) {

			if (dir == null || dir.isEmpty()) {
				continue;
			}

			ArrayList<String> fileList = new ArrayList<String>();
			getFilesList(dir, fileList);

			if (fileList.size() < 1)
				continue;

			for (String filePath : fileList) {
				logger.info("Reading file " + filePath + "... ");
				int res = readFile(filePath);
				if (res == 0)
					logger.info("successful.\n");
				else if (res == 3) {
					logger.error("Stopping program!");
					System.exit(-1);
				}
			}

		}
	}

	public void registerFile(String filePath) {
		// waiting = true;
		readFile(filePath);
		sortList();
	}

	private void sortList() {
		Collections.sort(fontInfoList, new Comparator<FontInfo>() {
			public int compare(FontInfo fm1, FontInfo fm2) {
				return fm1.getFontFamilyName().compareToIgnoreCase(
						fm2.getFontFamilyName());
			}
		});

		String fontName, lowercaseName;
		for (FontInfo fi : fontInfoList) {
			fontName = fi.getFontFamilyName();
			nameSet.add(fontName);
			lowercaseName = fontName.toLowerCase();
			lowercaseNameSet.add(lowercaseName);
			addFamily(lowercaseName, fi);
		}
		sortName();
	}

	private void sortName() {
		List<String> list = new ArrayList<String>(nameSet);
		Collections.sort(list, new Comparator<String>() {
			public int compare(String arg0, String arg1) {
				return arg0.compareToIgnoreCase(arg1);
			}
		});
		fontNames = list.toArray(new String[list.size()]);
	}

	public List<FontInfo> getFontInfoList() {
		return fontInfoList;
	}

	public String[] getRegisteredFamilies() {
		return fontNames;
	}

	public Set<String> getRegisteredFamiliesSet() {
		return nameSet;
	}

	public Document getUsedFontXmlDoc(MappingGroup mappingGroup) {
		String fontName;
		List<FontInfo> list = new ArrayList<FontInfo>();
		List<FontInfo> faList;
		for (Mapping mp : mappingGroup.getList()) {
			fontName = mp.getFormat().getAttribute("font-family");
			faList = getFamilyList(fontName);
			if (faList != null && faList.size() > 0) {
				list.addAll(faList);
			}
		}
		return getXmlDocByList(list);
	}

	public Document getXmlDoc() {
		return getXmlDocByList(getFontInfoList());// all font
	}

	private Document getXmlDocByList(List<FontInfo> list) {
		Document doc = DocumentHelper.createDocument();
		Element fonts = doc.addElement("fonts");
		fonts.addAttribute("register-system-font-folders", "true");
		Element fontPaths = fonts.addElement("font-paths");
		Element font, name;
		for (FontInfo fi : list) {
			font = fontPaths.addElement("font");
			name = font.addElement("name");
			name.setText(fi.getFontName());
			name.addAttribute("style", fi.getFontVariable());
			font.addElement("location").setText(fi.getFontPath());
		}
		return doc;
	}

	// TODO: Vergleichen soll nicht nur mit fontName?
	public boolean isLocalFont(String name) {
		return lowercaseNameSet.contains(name.toLowerCase());
	}

	private static int readFile(String filePath) {
		if (!filePath.endsWith(".otf") && !filePath.endsWith(".OTF")
				&& !filePath.endsWith(".ttf") && !filePath.endsWith(".TTF")) {
			logger.info("canceled. Unknown font extension!\n");
			return 2;
		}

		try {
			// TODO: eigene class schreiben, um font informationen
			// auszulesen
			logger.info("JVM free memory: " + Runtime.getRuntime().freeMemory()
					+ " Bytes");
			BaseFont baseFont = BaseFont.createFont(filePath,
					BaseFont.IDENTITY_H, false);
			if (baseFont != null) {
				String[][] ss = baseFont.getAllNameEntries();
				FontInfo fontInfo = new FontInfo();
				// String [][]famliyName=baseFont.getFamilyFontName();

				fontInfo.setFontFamilyName(baseFont.getFamilyFontName()[0][3]);
				fontInfo.setFontName(ss[1][4]);

				fontInfo.setFontVariable(ss[2][4]);
				fontInfo.setFontPath(filePath);

				fontInfoList.add(fontInfo);
			}
		} catch (Exception e) {
			logger.error("Error when analysing: " + e + "\n");
			return 1;
		} catch (OutOfMemoryError o) {
			logger.error("Out of memory! Please increase Java heap space.");
			return 3;
		}
		return 0;
	}

	private static void getFilesList(String strPath, List<String> list) {
		File dir = new File(strPath);
		File[] files = dir.listFiles();
		String filePath;
		if (files == null)
			return;

		for (File f : files) {
			filePath = f.getAbsolutePath();
			if (f.isDirectory()) {
				getFilesList(filePath, list);
			} else {
				list.add(f.getAbsolutePath());
			}
		}
	}

	public static void main(String args[]) {
		System.out.println(FontManager.getInstance().getXmlDoc().asXML());

		for (String s : FontManager.getInstance().getRegisteredFamilies()) {
			System.out.println(s);
		}
		System.out
				.println(FontManager.getInstance().isLocalFont("Wingdings 3"));

	}

}
